package cn.edu.swu.mhans.room.bean.xml;

import cn.edu.swu.mhans.room.bean.*;
import cn.edu.swu.mhans.room.bean.io.Resource;
import cn.edu.swu.mhans.room.bean.io.ResourceLoader;
import cn.edu.swu.mhans.common.util.FieldUtils;
import cn.edu.swu.mhans.common.util.StringUtils;
import com.google.common.base.Strings;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author ZhanJingbo
 * @version 1.0.0
 * Created on 2018/3/26
 */
public class XmlRoomBeanDefinitionReader extends AbstractRoomBeanDefinitionReader {


    private static final String PROPERTY_TAG = "property";
    private static final String MAP_TAG = "map";
    private static final String LIST_TAG = "list";

    public XmlRoomBeanDefinitionReader(ResourceLoader resourceLoader) {
        super(resourceLoader);
    }

    /**
     * 加载对应资源中的bean定义
     *
     * @param locations
     */
    @Override
    public void loadRoomBeanDefinitions(String locations) {
        Resource resource = getResourceLoader().getResource(locations);
        try {
            doLoadRoomBeanDefinition(resource.getInputStream());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void doLoadRoomBeanDefinition(InputStream inputStream) throws Exception {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder documentBuilder = factory.newDocumentBuilder();
        Document document = documentBuilder.parse(inputStream);
        // 解析并注册bean
        registerRoomBeanDefinition(document);
        inputStream.close();
    }


    private void registerRoomBeanDefinition(Document document) {
        Element root = document.getDocumentElement();
        NodeList beanNodeList = root.getElementsByTagName("bean");

        parseBeanDefinitions(beanNodeList);
    }

    private void parseBeanDefinitions(NodeList beanNodeList) {
        for (int i = 0; i < beanNodeList.getLength(); i++) {
            Node beanNode = beanNodeList.item(i);
            if (beanNode instanceof Element) {
                RoomBeanDefinition roomBeanDefinition = processRoomBeanDefinition((Element) beanNode);
                register(roomBeanDefinition);
            }
        }
    }

    private RoomBeanDefinition processRoomBeanDefinition(Element roomBeanElement) {
        try {
            String roomBeanName = roomBeanElement.getAttribute("name");
            String roomBeanClassString = roomBeanElement.getAttribute("class");
            if (null == roomBeanName || null == roomBeanClassString) {
                throw new IllegalArgumentException("room bean name or class is null");
            }
            Class roomBeanClass = Class.forName(roomBeanClassString);
            NodeList propertyNodeList = roomBeanElement.getChildNodes();

            RoomBeanDefinition roomBeanDefinition = new RoomBeanDefinition();
            roomBeanDefinition.setRoomBeanName(roomBeanName);
            roomBeanDefinition.setRoomBeanClass(roomBeanClass);

            if (null != propertyNodeList && propertyNodeList.getLength() > 0) {
                PropertyValues propertyValues = processRoomBeanPropertyValues(propertyNodeList, roomBeanClass);
                roomBeanDefinition.setPropertyValues(propertyValues);
            }
            return roomBeanDefinition;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
        return null;
    }


    private PropertyValues processRoomBeanPropertyValues(NodeList propertyNodeList, Class roomBeanClass) throws NoSuchFieldException, ClassNotFoundException {

        PropertyValues propertyValues = new PropertyValues();

        Map<String, Class> propertyFieldMap = new HashMap<>();
        Method[] methods = roomBeanClass.getMethods();
        for (Method method : methods) {
            String methodName = method.getName();
            if (methodName.startsWith("get")) {
                String propertyName = methodName.substring(3);
                propertyName = StringUtils.getLowerCaseStringForFirstChar(propertyName);
                propertyFieldMap.put(propertyName, method.getReturnType());
            }
        }
        for (int i = 0; i < propertyNodeList.getLength(); i++) {
            Node node = propertyNodeList.item(i);
            if (!(node instanceof Element)) {
                continue;
            }
            PropertyValue propertyValue = null;
            Element propertyElement = (Element) node;
            if (PROPERTY_TAG.equals(propertyElement.getTagName())) {
                propertyValue = handlePropertyValue(propertyElement, propertyFieldMap);
            }
            if (MAP_TAG.equals(propertyElement.getTagName())) {
                propertyValue = handleMapValue(propertyElement);
            }
            if (LIST_TAG.equals(propertyElement.getTagName())) {
                propertyValue = handleListValue(propertyElement);
            }
            propertyValues.addPropertyValue(propertyValue);
        }

        return propertyValues;
    }


    private PropertyValue handlePropertyValue(Element propertyElement, Map<String, Class> propertyClass) throws NoSuchFieldException {

        String propertyName = propertyElement.getAttribute("name");
        if (null == propertyName) {
            throw new IllegalArgumentException("property name is null");
        }
        String ref = propertyElement.getAttribute("ref");
        if (!Strings.isNullOrEmpty(ref)) {
            RoomBeanReference roomBeanReference = new RoomBeanReference(ref);
            return new PropertyValue(propertyName, roomBeanReference);
        }
        String valueString = propertyElement.getAttribute("value");
        Class fieldsClass = propertyClass.get(propertyName);
        Object propertyValue = FieldUtils.getFieldObject(valueString, fieldsClass);
        return new PropertyValue(propertyName, propertyValue);
    }

    private PropertyValue handleMapValue(Element mapElement) throws ClassNotFoundException {
        //取出map标签相关的属性
        String propertyName = mapElement.getAttribute("name");
        String valueType = mapElement.getAttribute("valueType");
        NodeList values = mapElement.getElementsByTagName("value");

        //遍历value标签生成map对象的key-value对
        Map<String, Object> propertyValue = new HashMap<>(values.getLength());
        for (int i = 0; i < values.getLength() && values.item(i) instanceof Element; i++) {
            Element valueElement = (Element) values.item(i);
            String key = valueElement.getAttribute("key");
            Object value;
            if (!Strings.isNullOrEmpty(valueType)) {
                // 使用value配置的值
                value = FieldUtils.getFieldObject(valueElement.getTextContent(), Class.forName(valueType));
            } else {
                // 使用引用值
                value = new RoomBeanReference(valueElement.getAttribute("ref"));
            }
            propertyValue.put(key, value);
        }
        // 对生成的对象进行封装
        RoomBeanCollectionReference collectionReference = new RoomBeanCollectionReference(RoomBeanCollectionReference.RoomBeanCollectionReferenceType.MAP.getValue(), valueType, propertyValue);
        return new PropertyValue(propertyName, collectionReference);
    }

    private PropertyValue handleListValue(Element listElement) throws ClassNotFoundException {
        String propertyName = listElement.getAttribute("name");
        String valueType = listElement.getAttribute("valueType");
        NodeList valueList = listElement.getElementsByTagName("value");

        List<Object> propertyValue = new ArrayList<>();
        for (int i = 0; i < valueList.getLength() && valueList.item(i) instanceof Element; i++) {
            Element valueElement = (Element) valueList.item(i);
            Object value;
            if (!Strings.isNullOrEmpty(valueType)) {
                // 使用value配置的值
                value = FieldUtils.getFieldObject(valueElement.getTextContent(), Class.forName(valueType));
            } else {
                // 使用引用值
                value = new RoomBeanReference(valueElement.getAttribute("ref"));
            }
            propertyValue.add(value);
        }
        RoomBeanCollectionReference collectionReference = new RoomBeanCollectionReference(RoomBeanCollectionReference.RoomBeanCollectionReferenceType.LIST.getValue(), valueType, propertyValue);
        return new PropertyValue(propertyName, collectionReference);
    }

    private void register(RoomBeanDefinition roomBeanDefinition) {
        this.getRoomBeanDefinition().put(roomBeanDefinition.getRoomBeanName(), roomBeanDefinition);
    }
}
